﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RobotBattle2.Scripting;
using RobotBattle2.Runtime;
using RobotBattle2.Utilities;
using System.Globalization;
using System.Collections;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace RobotBattle2.Interpreter {
	public sealed class InterpreterScriptRunner : ScriptRunnerBase {
		public InterpreterScriptRunner() {
			m_GlobalScope = new GlobalVariableScope();
			ResultVariable = m_GlobalScope.DefineGlobal("_result");
		}

		IVariable ResultVariable;
		public GlobalVariableScope GlobalScope { get { return m_GlobalScope; } }
		private GlobalVariableScope m_GlobalScope;

		public override void Initialize() {
			InvokeCore(Script.Methods["init"], null);
		}

		public override void RunTurn() {
			throw new NotImplementedException();
		}

		private object InvokeCore(MethodDeclaration method, IVariableScope scope, params object[] arguments) {
			Debug.WriteLine(method.Name, "Invoke");
			Debug.Indent();
			try {
				var frame = new CallStackFrame(
					method,
					scope.GetCurrentCallStackFrame(),
					m_GlobalScope
				);
				if (method.Parameters.Count != arguments.Length)
					throw new NotSupportedException("Wrong number of arguments to method '{0}'"); // TODO: Make the interpretter take in call-site information for error reporting
				for (int i = 0; i < method.Parameters.Count; i++) {
					var p = method.Parameters[i];
					var v = arguments[i].AsValue();
					Debug.WriteLine(
						string.Format("{0} = {1}", p, v),
						"Parameter"
					);
					frame
						.DefineLocal(p.Text)
						.Value = v;
				}
				var flowControl = ExecuteStatements(method.Statements, frame);
				switch (flowControl) {
					case FlowControlState.NextStatement:
					case FlowControlState.Return:
						break;
					case FlowControlState.BreakLoop:
						throw new NotSupportedException("'break' command outside loop");
					case FlowControlState.NextLoop:
						throw new NotSupportedException("'next' command outside loop");
					default: throw new NotSupportedException();
				}
				if (frame.ReturnValue != null) {
					Debug.WriteLine(
						frame.ReturnValue.AsValue(),
						"ReturnValue"
					);
				}
				return frame.ReturnValue;
			} finally {
				Debug.Unindent();
			}
		}

		private FlowControlState ExecuteStatements(StatementCollection statements, IVariableScope parentScope) {
			Debug.WriteLine("Begin", "Block");
			Debug.Indent();
			var scope = new VariableScope(parentScope);
			int index = 0;
			try {
				for (; index < statements.Count; ) {
					var state = ExecuteStatement(statements[index], scope);
					Debug.Indent();
					Debug.WriteLine(state, "Flow");
					Debug.Unindent();
					switch (state) {
						case FlowControlState.NextStatement:
							index++;
							break;
						case FlowControlState.NextLoop:
						case FlowControlState.BreakLoop:
						case FlowControlState.Return:
							return state;
						default: throw new NotSupportedException();
					}
				}
				return FlowControlState.NextStatement;
			} finally {
				Debug.Unindent();
				Debug.WriteLine("End", "Block");
			}
		}
		private FlowControlState ExecuteStatement(Statement statement, IVariableScope scope) {
			Debug.WriteLine(
				Regex.Replace(statement.ToString(), @"\s+", " "),
				"ExecuteStatement"
			);
			Debug.Indent();
			try {
				switch (statement.Type) {
					case StatementType.Expression:
						var result = ExecuteExpression(((ExpressionStatement)statement).Expression, scope);
						return FlowControlState.NextStatement;
					case StatementType.If:
						return ExecuteIfStatement((IfStatement)statement, scope);
					case StatementType.Assignment:
						return AssignmentStatement((AssignmentStatement)statement, scope);
					case StatementType.WhileLoop:
						return ExecuteWhileLoopStatement((WhileLoopStatement)statement, scope);
					case StatementType.BreakLoop:
						return FlowControlState.BreakLoop;
					case StatementType.NextLoop:
						return FlowControlState.NextLoop;
					case StatementType.VariableDeclaration:
						return ExecuteVariableDeclarationStatement((VariableDeclarationStatement)statement, scope);
					default: throw new NotImplementedException("Interpreter does not implement the " + statement.Type + " statement");
				}
			} finally {
				Debug.Unindent();
			}
		}
		private FlowControlState ExecuteIfStatement(IfStatement statement, IVariableScope scope) {
			var condition = ExecuteExpression(statement.Condition, scope).AsBoolean();
			if (condition) {
				return ExecuteStatements(statement.TrueStatements, scope);
			} else {
				return ExecuteStatements(statement.FalseStatements, scope);
			}
		}
		private FlowControlState ExecuteWhileLoopStatement(WhileLoopStatement statement, IVariableScope scope) {
			while (ExecuteExpression(statement.Condition, scope).AsBoolean()) {
				var flow = ExecuteStatements(statement.Statements, scope);
				switch (flow) {
					case FlowControlState.NextLoop:
					case FlowControlState.NextStatement:
						continue;
					case FlowControlState.BreakLoop:
						goto END_LOOP;
					case FlowControlState.Return:
						return flow;
				}
			}
		END_LOOP:
			return FlowControlState.NextStatement;
		}
		private FlowControlState AssignmentStatement(AssignmentStatement statement, IVariableScope scope) {
			var target = (IVariable)ExecuteExpression(statement.Target, scope);
			var value = ExecuteExpression(statement.Value, scope);
			target.Value = value.AsValue();
			return FlowControlState.NextStatement;
		}
		private FlowControlState ExecuteVariableDeclarationStatement(VariableDeclarationStatement statement, IVariableScope scope) {
			foreach (var item in statement.Variables) {
				var variable = scope.DefineLocal(item.Target.Text);
				if (item.InitialValue != null) {
					variable.Value = ExecuteExpression(item.InitialValue, scope);
				}
			}
			return FlowControlState.NextStatement;
		}

		private object ExecuteExpression(Expression expression, IVariableScope scope) {
			Debug.WriteLine(expression, "Expression");
			Debug.Indent();
			object returnValue = null;
			try {
				switch (expression.Type) {
					case ExpressionType.Binary:
						return returnValue = ExecuteBinaryExpression((BinaryExpression)expression, scope);
					case ExpressionType.Unary:
						return returnValue = ExecuteUnaryExpression((UnaryExpression)expression, scope);
					case ExpressionType.Variable:
						return returnValue = scope.Get(((VariableExpression)expression).Symbol.Text);
					case ExpressionType.Constant:
						return returnValue = ((ConstantExpression)expression).Value;
					case ExpressionType.Indexer: {
							var indexer = (IndexerExpression)expression;
							var target = (IVariable)ExecuteExpression(indexer.Target, scope);
							var index = ExecuteExpression(indexer.Index, scope);
							return returnValue = target.GetIndex(index.AsNumber());
						}
					case ExpressionType.Property: {
							var property = (PropertyExpression)expression;
							var target = (IVariable)ExecuteExpression(property.Target, scope);
							return returnValue = target.GetProperty(property.Property.Text);
						}
					default: throw new NotImplementedException("Interpreter does not implement the " + expression.Type + " expression");
				}
			} finally {
				this.ResultVariable.Value = returnValue.AsValue();
				if (returnValue != null)
					Debug.WriteLine(
						this.ResultVariable.Value,
						"ExpressionResult"
					);
				Debug.Unindent();
			}
		}
		private object ExecuteBinaryExpression(BinaryExpression expression, IVariableScope scope) {
			object left, right;
			switch (expression.Operator) { // these are for short-circuiting
				case BinaryOperator.Or:
					left = ExecuteExpression(expression.Left, scope);
					if (left.AsBoolean())
						return true;
					right = ExecuteExpression(expression.Right, scope);
					return right.AsBoolean();
				case BinaryOperator.And:
					left = ExecuteExpression(expression.Left, scope);
					if (!left.AsBoolean())
						return false;
					right = ExecuteExpression(expression.Right, scope);
					return right.AsBoolean();
			}
			left = ExecuteExpression(expression.Left, scope);
			right = ExecuteExpression(expression.Right, scope);
			var leftType = Value.GetTypeCode(left);
			var rightType = Value.GetTypeCode(right);
			Comparison<object> comparer;
			if (leftType == ValueTypeCode.String || rightType == ValueTypeCode.String) {
				comparer = (a, b) => String.Compare(a.AsString(), b.AsString());
			} else {
				comparer = (a, b) => Decimal.Compare(a.AsNumber(), b.AsNumber());
			}
			switch (expression.Operator) {
				case BinaryOperator.Add:
					if (leftType == ValueTypeCode.String || rightType == ValueTypeCode.String) {
						return left.AsString() + right.AsString();
					} else {
						return left.AsNumber() + right.AsNumber();
					}
				case BinaryOperator.Subtract:
					return left.AsNumber() - right.AsNumber();
				case BinaryOperator.Multiply:
					return left.AsNumber() * right.AsNumber();
				case BinaryOperator.Divide:
					return left.AsNumber() / right.AsNumber();
				case BinaryOperator.Modulo:
					return left.AsNumber() % right.AsNumber();
				case BinaryOperator.NotEqual:
					return comparer(left, right) != 0 ? 1.0m : 0.0m;
				case BinaryOperator.Equal:
					return comparer(left, right) == 0 ? 1.0m : 0.0m;
				case BinaryOperator.GreaterEqual:
					return comparer(left, right) >= 0 ? 1.0m : 0.0m;
				case BinaryOperator.Greater:
					return comparer(left, right) > 0 ? 1.0m : 0.0m;
				case BinaryOperator.Less:
					return comparer(left, right) < 0 ? 1.0m : 0.0m;
				case BinaryOperator.LessEqual:
					return comparer(left, right) <= 0 ? 1.0m : 0.0m;
				default: throw new NotImplementedException("Interpreter does not implement the " + expression.Operator + " binary operator");
			}
		}
		private object ExecuteUnaryExpression(UnaryExpression expression, IVariableScope scope) {
			var value = ExecuteExpression(expression.Expression, scope);
			switch (expression.Operator) {
				case UnaryOperator.Plus:
					return value.AsNumber();
				case UnaryOperator.Minus:
					return -value.AsNumber();
				default: throw new NotImplementedException("Interpreter does not implement the " + expression.Operator + " unary operator");
			}
		}
	}
}
